package in

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"sort"
	"strings"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
)

type G map[string]interface{}
type KeySet map[string]bool
type Nodes []*Node

func (g G) ToJson() (data []byte) {
	data, _ = json.Marshal(&g)
	bytes.ReplaceAll(data, []byte(","), []byte(",\n\t"))

	return
}

func (g G) String() (output string) {
	l := 0
	for k, v := range g {
		if l > 3 {
			break
		}
		output += fmt.Sprintf("%v : %v \n", k, v)
		l += 3
	}
	return
}

func ToJson(g G) (data []byte) {
	data, _ = json.Marshal(&g)
	bytes.ReplaceAll(data, []byte(","), []byte(",\n\t"))
	return
}

// Node base data struct for web and db. data save in Data, edege define for connected
type Node struct {
	ID            int             `json:"id"`
	Title         string          `json:"title"`
	Label         string          `json:"label"`
	ConnectedKeys map[string]bool `json:"connected_keys"`
	Keys          S               `json:"keys"`
	Data          G               `json:"data"`

	connected    Nodes
	whoconnected Nodes
}

func (ks KeySet) SortedList() (sortkeys []string) {

	for k := range ks {
		sortkeys = append(sortkeys, k)
	}
	sort.Slice(sortkeys, func(i, j int) bool {
		return byte(sortkeys[i][0]) > byte(sortkeys[j][0])
	})
	return
}

func (ks KeySet) WithJoin(sep string) string {
	nosortkey := []string{}
	for k := range ks {
		nosortkey = append(nosortkey, k)
	}
	sort.Slice(nosortkey, func(i, j int) bool {
		return byte(nosortkey[i][0]) > byte(nosortkey[j][0])
	})
	return strings.Join(nosortkey, sep)
}

func (node *Node) ToJson() (string, error) {
	var err error
	defer func() {
		if err != nil {
			log.Println("toJson error:", err)
		}
	}()

	byteData, err := json.Marshal(node)
	return string(byteData), err
}

func FromJson(jsonString string) (node *Node) {
	node = new(Node)
	json.Unmarshal([]byte(jsonString), node)
	return
}

// FromMap from valid map
func FromMap(mapdata map[string]interface{}) (node *Node, err error) {
	node = new(Node)
	if tmp, ok := mapdata["connected_keys"]; ok {
		if pa, ok := tmp.(primitive.A); ok {
			keys := make(KeySet)
			tkeys := []interface{}(pa)
			for _, k := range tkeys {
				if k.(string) == "connected_keys" {
					continue
				}
				keys[k.(string)] = true
				if v, ok := mapdata[k.(string)]; !ok {
					return nil, errors.New("can not trans this dict -> Node: lack key :" + k.(string))
				} else {
					node.Title += fmt.Sprintf("%s:%s\n", k, v)
				}
			}
			node.Data = mapdata
			node.ConnectedKeys = keys
			node.Label = keys.WithJoin(" | ")
		} else {
			return nil, errors.New("no connected_keys found!!")
		}

	}
	for k := range mapdata {
		node.Keys = append(node.Keys, k)
	}
	return
}

func (node *Node) Save() {
	node.Data["connected_keys"] = node.ConnectedKeys
	// var connected_ids []string
	// for _, other := node.connected{

	// }

	EntryMany("DataDash", "dataIn", []interface{}{node.Data})
}

func FromDB(filter map[string]interface{}) (nodes Nodes) {
	FindSome("DataDash", "dataIn", filter, func(one bson.M, _ int) {
		no, err := FromMap(one)
		if err != nil {
			log.Println("ilegal one:", err)
		} else {
			nodes = append(nodes, no)
		}
	})
	return
}

func NewNode(data map[string]interface{}, connectedKeys ...string) (node *Node, err error) {
	node = new(Node)

	for _, k := range connectedKeys {
		if v, ok := data[k]; ok {
			node.Title += fmt.Sprintf("%s:%s\n", k, v)
		} else {
			return nil, errors.New("can not trans this dict -> Node: lack key :" + k)
		}
		node.ConnectedKeys[k] = true
	}
	data["connected_keys"] = connectedKeys
	node.Data = data
	node.Label = strings.Join(connectedKeys, " | ")

	for k := range data {
		node.Keys = append(node.Keys, k)
	}
	return
}

func (nodes Nodes) AutoMatch(keys ...string) int {
	var filters []G
	var _ids KeySet

	for _, node := range nodes {
		_ids[node.Data["_id"].(string)] = true
		var filter G
		for _, key := range keys {
			if v, ok := node.Data[key]; ok {
				filter[key] = v
			}
		}
		if len(filter) > 0 {
			filters = append(filters, filter)
		}
		log.Println("Condition:", filter)
	}
	scaleFilter := G{"$or": filters}
	addCount := 0
	FindSome("DataDash", "dataIn", scaleFilter, func(one bson.M, _ int) {
		no, err := FromMap(one)
		if err != nil {
			log.Println("ilegal one:", err)
		} else {
			if _, ok := _ids[no.Data["_id"].(string)]; !ok {
				nodes = append(nodes, no)
				addCount++
			}
		}
	})
	return addCount

}

func (nodes Nodes) SaveAll() int64 {
	var datas []interface{}

	for _, node := range nodes {
		node.Data["connected_keys"] = node.ConnectedKeys
		datas = append(datas, node.Data)
	}
	return EntryMany("DataDash", "dataIn", datas)
}

func TestMongo() {
	var err error
	node, err := NewNode(G{
		"name":  "zyy",
		"phone": 123245,
		"some":  "from beijing",
	}, "name")

	node2, err := NewNode(G{
		"name":  "zyy2",
		"phone": 123245,
		"some":  "from tianjian",
	}, "name")

	node3, err := NewNode(G{
		"name":  "zyy3",
		"phone": 123245,
		"some":  "from tianjian",
		"addr":  "Be",
	}, "name", "addr")

	if err != nil {
		log.Fatal(err)
	}

	Nodes{node, node2, node3}.SaveAll()
	objID, _ := primitive.ObjectIDFromHex("5ea2927d5a075a037020e307")
	for _, v := range FromDB(G{
		"_id": objID,
	}) {
		fmt.Println(v.ToJson())
	}

}
